package hos.biometric;

/**
 * <p>Title: AuthenticationCallbackProvider </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2022/11/18 10:17
 */

import android.annotation.SuppressLint;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;


/**
 * Uses a common listener interface provided by the client to create and cache authentication
 * callback objects that are compatible with {@link android.hardware.biometrics.BiometricPrompt} or
 * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
 */
@SuppressWarnings("deprecation")
class AuthenticationCallbackProvider {
 /**
  * A listener object that can receive events from either
  * {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} or
  * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback}.
  */
 static class Listener {
  /**
   * See {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
   * BiometricPrompt.AuthenticationResult)}.
   *
   * @param result An object containing authentication-related data.
   */
  void onSuccess(@NonNull BiometricPrompt.AuthenticationResult result) {}

  /**
   * See {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int,
   * CharSequence)}.
   *
   * @param errorCode    An integer ID associated with the error.
   * @param errorMessage A human-readable message that describes the error.
   */
  void onError(int errorCode, @Nullable CharSequence errorMessage) {}

  /**
   * Called when a recoverable error/event has been encountered during authentication.
   *
   * @param helpMessage A human-readable message that describes the event.
   */
  void onHelp(@Nullable CharSequence helpMessage) {}

  /**
   * See {@link BiometricPrompt.AuthenticationCallback#onAuthenticationFailed()}.
   */
  void onFailure() {}
 }

 /**
  * An authentication callback object that is compatible with
  * {@link android.hardware.biometrics.BiometricPrompt}.
  */
 @Nullable
 private android.hardware.biometrics.BiometricPrompt.AuthenticationCallback mBiometricCallback;

 /**
  * An authentication callback object that is compatible with
  * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
  */
 @Nullable
 private android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
         mFingerprintCallback;

 /**
  * A common listener object that will receive all authentication events.
  */
 @SuppressWarnings("WeakerAccess") /* synthetic access */
 @NonNull
 final Listener mListener;

 /**
  * Constructs a callback provider that delegates events to the given listener.
  *
  * @param listener A listener that will receive authentication events.
  */
 AuthenticationCallbackProvider(@NonNull Listener listener) {
  mListener = listener;
 }

 /**
  * Provides a callback object that wraps the given listener and is compatible with
  * {@link android.hardware.biometrics.BiometricPrompt}.
  *
  * <p>Subsequent calls to this method for the same provider instance will return the same
  * callback object.
  *
  * @return A callback object that can be passed to
  * {@link android.hardware.biometrics.BiometricPrompt}.
  */
 @RequiresApi(Build.VERSION_CODES.P)
 @NonNull
 android.hardware.biometrics.BiometricPrompt.AuthenticationCallback getBiometricCallback() {
  if (mBiometricCallback == null) {
   mBiometricCallback = Api28Impl.createCallback(mListener);
  }
  return mBiometricCallback;
 }

 /**
  * Provides a callback object that wraps the given listener and is compatible with
  * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
  *
  * <p>Subsequent calls to this method for the same provider instance will return the same
  * callback object.
  *
  * @return A callback object that can be passed to
  * {@link android.support.v4.hardware.fingerprint.FingerprintManagerCompat}.
  */
 @NonNull
 android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
 getFingerprintCallback() {
  if (mFingerprintCallback == null) {
   mFingerprintCallback = new android.support.v4.hardware.fingerprint.FingerprintManagerCompat
           .AuthenticationCallback() {
    @Override
    public void onAuthenticationError(final int errMsgId, CharSequence errString) {
     mListener.onError(errMsgId, errString);
    }

    @Override
    public void onAuthenticationHelp(int helpMsgId, final CharSequence helpString) {
     mListener.onHelp(helpString);
    }

    @Override
    public void onAuthenticationSucceeded(final android.support.v4.hardware.fingerprint
            .FingerprintManagerCompat.AuthenticationResult result) {

     final BiometricPrompt.CryptoObject crypto = result != null
             ? CryptoObjectUtils.unwrapFromFingerprintManager(
             result.getCryptoObject())
             : null;

     final BiometricPrompt.AuthenticationResult resultCompat =
             new BiometricPrompt.AuthenticationResult(
                     crypto, BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);

     mListener.onSuccess(resultCompat);
    }

    @Override
    public void onAuthenticationFailed() {
     mListener.onFailure();
    }
   };
  }
  return mFingerprintCallback;
 }

 /**
  * Nested class to avoid verification errors for methods introduced in Android 11 (API 30).
  */
 @RequiresApi(Build.VERSION_CODES.R)
 private static class Api30Impl {
  // Prevent instantiation.
  private Api30Impl() {}

  /**
   * Gets the authentication type from the given framework authentication result.
   *
   * @param result An instance of
   *               {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}.
   * @return The value returned by calling {@link
   * android.hardware.biometrics.BiometricPrompt.AuthenticationResult#getAuthenticationType()}
   * for the given result object.
   */
  @SuppressLint("WrongConstant")
  @BiometricPrompt.AuthenticationResultType
  static int getAuthenticationType(
          @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
   return result.getAuthenticationType();
  }
 }

 /**
  * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
  */
 @RequiresApi(Build.VERSION_CODES.P)
 private static class Api28Impl {
  // Prevent instantiation.
  private Api28Impl() {}

  /**
   * Creates a {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} that
   * delegates events to the given listener.
   *
   * @param listener A listener object that will receive authentication events.
   * @return A new instance of
   * {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}.
   */
  @NonNull
  static android.hardware.biometrics.BiometricPrompt.AuthenticationCallback createCallback(
          @NonNull final Listener listener) {
   return new android.hardware.biometrics.BiometricPrompt.AuthenticationCallback() {
    @Override
    public void onAuthenticationError(int errorCode, CharSequence errString) {
     listener.onError(errorCode, errString);
    }

    @Override
    public void onAuthenticationHelp(
            final int helpCode, final CharSequence helpString) {
     // Don't forward the result to the client, since the dialog takes care of it.
    }

    @Override
    public void onAuthenticationSucceeded(
            android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {

     final BiometricPrompt.CryptoObject crypto = result != null
             ? CryptoObjectUtils.unwrapFromBiometricPrompt(result.getCryptoObject())
             : null;

     @BiometricPrompt.AuthenticationResultType final int authenticationType;
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      authenticationType = result != null
              ? Api30Impl.getAuthenticationType(result)
              : BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
     } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
      authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
     } else {
      authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
     }

     final BiometricPrompt.AuthenticationResult resultCompat =
             new BiometricPrompt.AuthenticationResult(crypto, authenticationType);

     listener.onSuccess(resultCompat);
    }

    @Override
    public void onAuthenticationFailed() {
     listener.onFailure();
    }
   };
  }
 }
}

